<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>Egg.js 与 Koa - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/intro/egg-and-koa.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/intro/egg-and-koa.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Egg.js 与 Koa</h1>
    <h2 id="异步编程模型"><a class="markdown-anchor" href="#异步编程模型">#</a> 异步编程模型</h2>
<p>Node.js 是一个异步的世界，官方 API 支持的都是 callback 形式的异步编程模型，这会带来许多问题，例如</p>
<ul>
<li><a href="http://callbackhell.com/" target="_blank" rel="noopener">callback hell</a>: 最臭名昭著的 callback 嵌套问题。</li>
<li><a href="https://oren.github.io/blog/zalgo.html" target="_blank" rel="noopener">release zalgo</a>: 异步函数中可能同步调用 callback 返回数据，带来不一致性。</li>
</ul>
<p>因此社区提供了各种异步的解决方案，最终胜出的是 Promise，它也内置到了 ECMAScript 2015 中。而在 Promise 的基础上，结合 Generator 提供的切换上下文能力，出现了 <a href="https://github.com/tj/co" target="_blank" rel="noopener">co</a> 等第三方类库来让我们用同步写法编写异步代码。同时，<a href="https://github.com/tc39/ecmascript-asyncawait" target="_blank" rel="noopener">async function</a> 这个官方解决方案也于 ECMAScript 2017 中发布，并在 Node.js 8 中实现。</p>
<h3 id="async-function"><a class="markdown-anchor" href="#async-function">#</a> async function</h3>
<p><a href="https://github.com/tc39/ecmascript-asyncawait" target="_blank" rel="noopener">async function</a> 是语言层面提供的语法糖，在 async function 中，我们可以通过 <code>await</code> 关键字来等待一个 Promise 被 resolve（或者 reject，此时会抛出异常）， Node.js 现在的 LTS 版本（8.x）已原生支持。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> fn = <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="keyword">await</span> getUser();</span><br><span class="line">  <span class="keyword">const</span> posts = <span class="keyword">await</span> fetchPosts(user.id);</span><br><span class="line">  <span class="keyword">return</span> &#123; user, posts &#125;;</span><br><span class="line">&#125;;</span><br><span class="line">fn().then(<span class="function"><span class="params">res</span> =&gt;</span> <span class="built_in">console</span>.log(res)).catch(<span class="function"><span class="params">err</span> =&gt;</span> <span class="built_in">console</span>.error(err.stack));</span><br></pre></td></tr></table></figure>
<h2 id="koa"><a class="markdown-anchor" href="#koa">#</a> Koa</h2>
<blockquote>
<p>Koa is a new Web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for Web applications and APIs.</p>
</blockquote>
<p>Koa 和 Express 的设计风格非常类似，底层也都是共用的<a href="https://github.com/jshttp" target="_blank" rel="noopener">同一套 HTTP 基础库</a>，但是有几个显著的区别，除了上面提到的默认异步解决方案之外，主要的特点还有下面几个。</p>
<h3 id="middleware"><a class="markdown-anchor" href="#middleware">#</a> Middleware</h3>
<p>Koa 的中间件和 Express 不同，Koa 选择了洋葱圈模型。</p>
<ul>
<li>中间件洋葱图：</li>
</ul>
<p><img src="https://camo.githubusercontent.com/d80cf3b511ef4898bcde9a464de491fa15a50d06/68747470733a2f2f7261772e6769746875622e636f6d2f66656e676d6b322f6b6f612d67756964652f6d61737465722f6f6e696f6e2e706e67" alt=""></p>
<ul>
<li>中间件执行顺序图：</li>
</ul>
<p><img src="https://raw.githubusercontent.com/koajs/koa/a7b6ed0529a58112bac4171e4729b8760a34ab8b/docs/middleware.gif" alt=""></p>
<p>所有的请求经过一个中间件的时候都会执行两次，对比 Express 形式的中间件，Koa 的模型可以非常方便的实现后置处理逻辑，对比 Koa 和 Express 的 Compress 中间件就可以明显的感受到 Koa 中间件模型的优势。</p>
<ul>
<li><a href="https://github.com/koajs/compress/blob/master/index.js" target="_blank" rel="noopener">koa-compress</a> for Koa.</li>
<li><a href="https://github.com/expressjs/compression/blob/master/index.js" target="_blank" rel="noopener">compression</a> for Express.</li>
</ul>
<h3 id="context"><a class="markdown-anchor" href="#context">#</a> Context</h3>
<p>和 Express 只有 Request 和 Response 两个对象不同，Koa 增加了一个 Context 的对象，作为这次请求的上下文对象（在 Koa 1 中为中间件的 <code>this</code>，在 Koa 2 中作为中间件的第一个参数传入）。我们可以将一次请求相关的上下文都挂载到这个对象上。类似 <a href="https://github.com/eggjs/egg-tracer/blob/1.0.0/lib/tracer.js#L12" target="_blank" rel="noopener">traceId</a> 这种需要贯穿整个请求（在后续任何一个地方进行其他调用都需要用到）的属性就可以挂载上去。相较于 request 和 response 而言更加符合语义。</p>
<p>同时 Context 上也挂载了 Request 和 Response 两个对象。和 Express 类似，这两个对象都提供了大量的便捷方法辅助开发，例如</p>
<ul>
<li><code>get request.query</code></li>
<li><code>get request.hostname</code></li>
<li><code>set response.body</code></li>
<li><code>set response.status</code></li>
</ul>
<h3 id="异常处理"><a class="markdown-anchor" href="#异常处理">#</a> 异常处理</h3>
<p>通过同步方式编写异步代码带来的另外一个非常大的好处就是异常处理非常自然，使用 <code>try catch</code> 就可以将按照规范编写的代码中的所有错误都捕获到。这样我们可以很便捷的编写一个自定义的错误处理中间件。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">onerror</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    ctx.app.emit(<span class="string">'error'</span>, err);</span><br><span class="line">    ctx.body = <span class="string">'server error'</span>;</span><br><span class="line">    ctx.status = err.status || <span class="number">500</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>只需要将这个中间件放在其他中间件之前，就可以捕获它们所有的同步或者异步代码中抛出的异常了。</p>
<h2 id="egg-继承于-koa"><a class="markdown-anchor" href="#egg-继承于-koa">#</a> Egg 继承于 Koa</h2>
<p>如上述，Koa 是一个非常优秀的框架，然而对于企业级应用来说，它还比较基础。</p>
<p>而 Egg 选择了 Koa 作为其基础框架，在它的模型基础上，进一步对它进行了一些增强。</p>
<h3 id="扩展"><a class="markdown-anchor" href="#扩展">#</a> 扩展</h3>
<p>在基于 Egg 的框架或者应用中，我们可以通过定义 <code>app/extend/{application,context,request,response}.js</code> 来扩展 Koa 中对应的四个对象的原型，通过这个功能，我们可以快速的增加更多的辅助方法，例如我们在 <code>app/extend/context.js</code> 中写入下列代码：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/context.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  get isIOS() &#123;</span><br><span class="line">    <span class="keyword">const</span> iosReg = <span class="regexp">/iphone|ipad|ipod/i</span>;</span><br><span class="line">    <span class="keyword">return</span> iosReg.test(<span class="keyword">this</span>.get(<span class="string">'user-agent'</span>));</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>在 Controller 中，我们就可以使用到刚才定义的这个便捷属性了：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/home.js</span></span><br><span class="line">exports.handler = <span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">  ctx.body = ctx.isIOS</span><br><span class="line">    ? <span class="string">'Your operating system is iOS.'</span></span><br><span class="line">    : <span class="string">'Your operating system is not iOS.'</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>更多关于扩展的内容，请查看<a href="../basics/extend.html">扩展</a>章节。</p>
<h3 id="插件"><a class="markdown-anchor" href="#插件">#</a> 插件</h3>
<p>众所周知，在 Express 和 Koa 中，经常会引入许许多多的中间件来提供各种各样的功能，例如引入 <a href="https://github.com/koajs/session" target="_blank" rel="noopener">koa-session</a> 提供 Session 的支持，引入 <a href="https://github.com/koajs/bodyparser" target="_blank" rel="noopener">koa-bodyparser</a> 来解析请求 body。而 Egg 提供了一个更加强大的插件机制，让这些独立领域的功能模块可以更加容易编写。</p>
<p>一个插件可以包含</p>
<ul>
<li>extend：扩展基础对象的上下文，提供各种工具类、属性。</li>
<li>middleware：增加一个或多个中间件，提供请求的前置、后置处理逻辑。</li>
<li>config：配置各个环境下插件自身的默认配置项。</li>
</ul>
<p>一个独立领域下的插件实现，可以在代码维护性非常高的情况下实现非常完善的功能，而插件也支持配置各个环境下的默认（最佳）配置，让我们使用插件的时候几乎可以不需要修改配置项。</p>
<p><a href="https://github.com/eggjs/egg-security" target="_blank" rel="noopener">egg-security</a> 插件就是一个典型的例子。</p>
<p>更多关于插件的内容，请查看<a href="../basics/plugin.html">插件</a>章节。</p>
<h3 id="egg-与-koa-的版本关系"><a class="markdown-anchor" href="#egg-与-koa-的版本关系">#</a> Egg 与 Koa 的版本关系</h3>
<h4 id="egg-1x"><a class="markdown-anchor" href="#egg-1x">#</a> Egg 1.x</h4>
<p>Egg 1.x 发布时，Node.js 的 LTS 版本尚不支持 async function，所以 Egg 1.x 仍然基于 Koa 1.x 开发，但是在此基础上，Egg 全面增加了 async function 的支持，再加上 Egg 对 Koa 2.x 的中间件也完全兼容，应用层代码可以完全基于 <code>async function</code> 来开发。</p>
<ul>
<li>底层基于 Koa 1.x，异步解决方案基于 <a href="https://github.com/tj/co" target="_blank" rel="noopener">co</a> 封装的 generator function。</li>
<li>官方插件以及 Egg 核心使用 generator function 编写，保持对 Node.js LTS 版本的支持，在必要处通过 co 包装以兼容在 async function 中的使用。</li>
<li>应用开发者可以选择 async function（Node.js 8.x+） 或者 generator function（Node.js 6.x+）进行编写。</li>
</ul>
<h4 id="egg-2x"><a class="markdown-anchor" href="#egg-2x">#</a> Egg 2.x</h4>
<p>Node.js 8 正式进入 LTS 后，async function 可以在 Node.js 中使用并且没有任何性能问题了，Egg 2.x 基于 Koa 2.x，框架底层以及所有内置插件都使用 async function 编写，并保持了对 Egg 1.x 以及 generator function 的完全兼容，应用层只需要升级到 Node.js 8 即可从 Egg 1.x 迁移到 Egg 2.x。</p>
<ul>
<li>底层基于 Koa 2.x，异步解决方案基于 async function。</li>
<li>官方插件以及 Egg 核心使用 async function 编写。</li>
<li>建议业务层迁移到 async function 方案。</li>
<li>只支持 Node.js 8 及以上的版本。</li>
</ul>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/intro/egg-and-koa.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/intro/egg-and-koa.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
